package com.cykj.pos.util;


import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * RSA签名工具类
 * @author yangyuanyuan
 * @version 1.0
 * @date 2020/9/21 16:26
 */
public class RSASignUtil {

    private RSASignUtil() {}


    /**
     * 得到私钥
     *
     * @param key 密钥字符串（经过base64编码）
     * @throws Exception
     */
    public static PrivateKey getPrivateKey(String key) throws Exception {
        if (StringUtils.isBlank(key)) {
            return null;
        }
        byte[] keyBytes = Base64.decodeBase64(key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        return privateKey;
    }

    /**
     * 得到公钥
     *
     * @param key 密钥字符串（经过base64编码）
     * @throws Exception
     */
    public static PublicKey getPublicKey(String key) throws Exception {
        if (StringUtils.isBlank(key)) {
            return null;
        }
        byte[] publicKeyBytes = Base64.decodeBase64(key);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
        return publicKey;
    }

    /**
     * 生成签名
     * @param reqMap 需要加签请求参数
     * @param key 密钥字符串（经过base64编码）
     * @param ignoreFields 排除不需要参加签名的字段
     * @return
     */
    public static String generateSign (Map<String, String> reqMap, String key, List<String> ignoreFields) throws Exception {
        if (reqMap == null || reqMap.isEmpty()) {
            return StringUtils.EMPTY;
        }
        if (StringUtils.isBlank(key)) {
            return StringUtils.EMPTY;
        }

        String content = assembleSignContent(reqMap, ignoreFields);

        return generateSign(content, key);
    }

    /**
     * 组装签名字符串
     * @param reqMap
     * @param ignoreFields
     * @return
     */
    private static String assembleSignContent (Map<String, String> reqMap, List<String> ignoreFields) {
        TreeMap<String, String> reqTreeMap = new TreeMap<String, String>();
        for (Map.Entry<String, String> entry : reqMap.entrySet()) {
            if (ignoreFields != null && ignoreFields.contains(entry.getKey())) {
                continue;
            }
            reqTreeMap.put(entry.getKey(), entry.getValue());
        }
        StringBuilder signStr = new StringBuilder();
        for (Map.Entry<String, String> item : reqTreeMap.entrySet()) {
            if (StringUtils.isNotBlank(signStr.toString())) {
                signStr.append("&");
            }
            signStr.append(item.getKey()).append("=").append(item.getValue());
        }
        return signStr.toString();
    }

    /**
     * 生成签名
     * @param signStr 生成好的签名字符串
     * @param key  密钥字符串（经过base64编码）
     * @return
     * @throws Exception
     */
    public static String generateSign(String signStr, String key)throws Exception {
        PrivateKey privateKey = getPrivateKey(key);
        Signature signature = Signature.getInstance("SHA1WithRSA");
        signature.initSign(privateKey);
        signature.update(signStr.getBytes());
        return Base64.encodeBase64String(signature.sign());
    }

    /**
     * 验证签名
     * @param reqMap 需要加签请求参数
     * @param key    密钥字符串（经过base64编码）
     * @param ignoreFields 排除不需要参加签名的字段
     * @param mac    签名值
     * @return
     * @throws Exception
     */
    public static boolean verifySign (Map<String, String> reqMap, String key, List<String> ignoreFields, String mac)throws Exception {
        PublicKey publicKey = getPublicKey(key);
        Signature signature = Signature.getInstance("SHA1WithRSA");
        if (StringUtils.isBlank(mac)) {
            return false;
        }
        signature.initVerify(publicKey);
        String content = assembleSignContent(reqMap, ignoreFields);
        signature.update(content.getBytes());
        return signature.verify(Base64.decodeBase64(mac));
    }
}
